#Teatime odf parser

import os, sys
from Sanae3D.lib.ModelObject import Model3D
from Sanae3D.lib.StructReader import StructReader

#TO DO: yuki hotaru half-complete
#messy frame detection - REDO

#odf formats:
#general format is 0
#1 - love death 3
#2 - HCT
#
#4 - yuki hotaru


class teatime_odf(Model3D):    
    
    def __init__(self, obj3D=None, outFile=None, inFile=None):
        
        super(teatime_odf, self).__init__("ODF")
        self.obj3D = obj3D
        self.inFile = inFile
        self.outFile = outFile
        self.tempMat = {}
        self.tempTex = {}
        self.tempFrame = {} #frame ID mappnig
        self.submeshList = [] #temp storage
        self.meshList = []
        self.tempMesh = {} #mesh ID mapping
        self.odfFormat = 0         
    #===parser
        
    def read_name(self, n):
        
        return self.inFile.read_string(n).split("\x00")[0]
        
    def parse_textures(self, sectLen):
        
        #check if it's HCT (after school custom time)
        if sectLen % 204 != 0:
            self.odfFormat = 2
            numTex = sectLen / 132
            for i in xrange(numTex):
                name = self.read_name(64)
                texID = self.inFile.read_long()
                texFile = self.read_name(64)
                self.tempTex[texID] = texFile
                
        #most older ODF format
        else:
            numTex = sectLen / 204
            for i in xrange(numTex):
                name = self.read_name(64)
                texID = self.inFile.read_long()
                texFile = self.read_name(136)
                self.tempTex[texID] = texFile
        
    def parse_materials(self, sectLen):
        
        numMat = sectLen / 140
        for i in xrange(numMat):
            name = self.read_name(64)
            matID = self.inFile.read_long()
            ambient = self.inFile.read_float(4)
            diffuse = self.inFile.read_float(4)
            specular = self.inFile.read_float(4)
            emissive = self.inFile.read_float(4)
            self.inFile.read_float()
            power = self.inFile.read_float()
            #print matID
            self.create_material(i)
            self.add_material_name(i, name)
            self.add_ambient(i, ambient)
            self.add_diffuse(i, diffuse)
            self.add_specular(i, specular)
            self.add_emissive(i, emissive)
            self.add_power(i, power)
            self.tempMat[matID] = i
        
    def parse_faces(self, meshName, numFaces, matNum):
        
        for i in xrange(numFaces):
            v1, v2, v3 = self.inFile.read_short(3)
            self.create_face(meshName, i)
            self.add_face_verts(meshName, i, [v1, v2, v3])
            self.add_face_material(meshName, i, matNum)
        
    def parse_vertices(self, meshName, numVerts):
        
        for i in xrange(numVerts):
            vx, vy, vz, w1, w2, w3, w4, nx, ny, nz = self.inFile.read_float(10)
            b1, b2, b3, b4 = self.inFile.read_byte(4)
            tu, tv = self.inFile.read_float(2)
            
            vx = float(vx)
            self.create_vertex(meshName, i)
            self.add_coords(meshName, i, [vx, vy, vz])
            self.add_vert_uv(meshName, i, [tu, tv])
            self.add_vert_normals(meshName, i, [nx, ny, nz])
            self.add_vert_weights(meshName, i, [w1, w2, w3, w4])
            self.add_vert_bones(meshName, i, [b1, b2, b3, b4])
            
    def parse_yuki_vertices(self, meshName, numVerts):
        
        for i in xrange(numVerts):
            vx, vy, vz, unk, nx, ny, nz, tu, tv = self.inFile.read_float(9)
            self.create_vertex(meshName, i)
            self.add_coords(meshName, i, [vx, vy, vz])
            self.add_vert_normals(meshName, i, [nx, ny, nz])
            self.add_vert_uv(meshName, i, [tu, tv])
            
    def parse_yuki_submesh(self, meshName, meshNum):
        
        self.create_object(meshName)
        meshID, matNum, null, matID, texID = self.inFile.read_long(5)
        texInfo = self.inFile.read_long(13)
        numVerts = self.inFile.read_long()
        numFaces = self.inFile.read_long() / 3
        matFlag = self.inFile.read_long()
        self.inFile.seek(140, 1)
        self.parse_yuki_vertices(meshName, numVerts)
        self.parse_faces(meshName, numFaces, matNum)
        
        self.tempMesh[meshID] = meshNum
        
        self.submeshList[meshNum].append(meshName)
            
    def parse_HCT_submesh(self, meshNum):
        
        meshCount = self.mesh_count()
        meshName = self.read_name(64)
        self.create_object(meshName)
        self.submeshList[meshNum].append(meshName)
        meshID, matNum, null, matID, texID = self.inFile.read_long(5)
        texInfo = self.inFile.read_long(9)
        numVerts = self.inFile.read_long()
        numFaces = self.inFile.read_long() / 3
        matFlag = self.inFile.read_long(4)
        matNum = self.tempMat[matID]
        if texID != 0:
            self.add_texture_name(matNum, self.tempTex[texID])
        self.parse_vertices(meshName, numVerts)
        self.parse_faces(meshName, numFaces, matNum)
        self.inFile.read_long(6)               #null
        
        self.meshList.append(meshName)
        self.tempMesh[meshID] = meshCount
        
    def parse_LD_submesh(self, meshNum):
        
        meshCount = self.mesh_count()
        meshName = self.read_name(64)
        self.create_object(meshName)
        self.submeshList[meshNum].append(meshName)
        
        meshID, matNum, null, matID, texID = self.inFile.read_long(5)
        texInfo = self.inFile.read_long(13)
        numVerts = self.inFile.read_long()
        numFaces = self.inFile.read_long() / 3
        matFlag = self.inFile.read_long(4)
        self.inFile.seek(432, 1)   # nulls
        if matID:
            matNum = self.tempMat[matID]
            if texID != 0:
                self.add_texture_name(matNum, self.tempTex[texID])
        self.parse_vertices(meshName, numVerts)
        self.parse_faces(meshName, numFaces, matNum)
        self.inFile.read_long(6)               #nulls
        
        self.meshList.append(meshName)
        self.tempMesh[meshID] = meshCount
        
    def parse_meshes(self, sectLen):
        
        sectEnd = self.inFile.tell() + sectLen
        meshNum = 0
        while self.inFile.tell() != sectEnd:
            
            name = self.read_name(64)
            if self.odfFormat == 4:
                self.submeshList.append([])
                self.parse_yuki_submesh(name, meshNum)
            else:
                meshID, numSubMesh = self.inFile.read_long(2)
                self.tempMesh[meshID] = meshNum
                
                self.submeshList.append([])
                for i in xrange(numSubMesh):
                    if self.odfFormat == 2:
                        self.parse_HCT_submesh(meshNum)
                    else:
                        self.parse_LD_submesh(meshNum)
                    
            meshNum += 1
        
    def parse_frames(self, sectLen):
        
        if sectLen % 400 == 0:
            self.odfFormat = 4
            numFrames = sectLen / 400
            for i in xrange(numFrames):
                frameName = self.read_name(64)
                frameID = self.inFile.read_long()
                frameMatrix = self.inFile.read_float(16)
                self.inFile.seek(44, 1)   #11 null
                parentID, meshID = self.inFile.read_long(2)
                self.inFile.seek(216, 1)
            
                self.tempFrame[frameID] = i                
                
                self.create_frame(i)
                self.add_frame_name(i, frameName)
                self.add_frame_transform(i, frameMatrix)

                if parentID:
                    parentIndex = self.tempFrame[parentID]
                    self.add_frame_parent(i, parentIndex)
                    self.add_frame_child(parentIndex, frameName)
                if meshID:
                    submeshes = self.submeshList[self.tempMesh[meshID]]
                    for mesh in submeshes:
                        self.add_frame_mesh(i, mesh)

        elif sectLen % 976 == 0:
            numFrames = sectLen / 976
            for i in xrange(numFrames):
                frameName = self.read_name(64)
                frameID = self.inFile.read_long()
                frameMatrix = self.inFile.read_float(16)
                
                self.inFile.seek(44, 1)   #11 null
                parentID, meshID = self.inFile.read_long(2)
                self.inFile.seek(220, 1)  #55 null
                #bbMin = self.inFile.read_float(3)
                #bbMax = self.inFile.read_float(3)
                self.inFile.read_float(12)
                self.inFile.seek(508, 1)  #127 null
                float2 = self.inFile.read_float(4)
                self.tempFrame[frameID] = i                
                self.create_frame(i)
                self.add_frame_name(i, frameName)
                self.add_frame_transform(i, frameMatrix)
                #self.add_frame_bbox(i, bbMin)
                #self.add_frame_bbox(i, bbMax)
                if parentID:
                    parentIndex = self.tempFrame[parentID]
                    self.add_frame_parent(i, parentIndex)
                    self.add_frame_child(parentIndex, frameName)
                if meshID:
                    submeshes = self.submeshList[self.tempMesh[meshID]]
                    for mesh in submeshes:
                        self.add_frame_mesh(i, mesh)
                        
        elif sectLen % 720 == 0:
            #love death 3 maps
            self.odfFormat = 1
            numFrames = sectLen / 720
            for i in xrange(numFrames):
                frameName = self.read_name(64)
                frameID = self.inFile.read_long()
                frameMatrix = self.inFile.read_float(16)
                self.inFile.seek(40, 1) #null
                self.inFile.read_long(2)
                self.inFile.seek(228, 1) #null
                self.inFile.read_long(4)
                self.inFile.seek(296, 1) #??
                
                self.tempFrame[frameID] = i                
                
                self.create_frame(i)
                self.add_frame_name(i, frameName)
                self.add_frame_transform(i, frameMatrix)
                #self.add_frame_bbox(i, bbMin)
                #self.add_frame_bbox(i, bbMax)
                
                if parentID:
                    parentIndex = self.tempFrame[parentID]
                    self.add_frame_parent(i, parentIndex)
                    self.add_frame_child(parentIndex, frameName)
                if meshID:
                    submeshes = self.submeshList[self.tempMesh[meshID]]
                    for mesh in submeshes:
                        self.add_frame_mesh(i, mesh)
        else:
            print "unknown"

    def parse_bones(self, sectLen):
        '''Reference mesh by meshID. Bones are stored in the order that
        they are referenced by the mesh'''

        boneIndex = 0
        index = self.inFile.read_long()
        self.inFile.seek(64, 1)
        numMesh = self.inFile.read_long()
        for i in xrange(numMesh):
            null = self.inFile.read_long()
            self.inFile.seek(64, 1)
            #boneName = self.inFile.read_string(64, '\x00')
            frameID =  self.inFile.read_long()
            meshID = self.inFile.read_long()
            numBones = self.inFile.read_long()
            meshName = self.meshList[self.tempMesh[meshID]]
            frameName = self._frames[self.tempFrame[frameID] + i + 1].get_name()
            #print boneName
            for j in xrange(numBones):
                boneID = self.inFile.read_long()
                num3 = self.inFile.read_long()
                self.inFile.seek(num3*24, 1)
                indices = self.inFile.read_long(num3)
                values = self.inFile.read_long(num3)
                transform = self.inFile.read_float(16)
                boneName = frameName
                self.add_mesh_bone(meshName, j)
                self.create_bone(boneIndex)
                self.add_bone_transform(boneIndex, transform)
                self.add_bone_name(boneIndex, boneName)
                boneIndex += 1

    def parse_file(self):
        '''Main parser method. Can be replaced'''
        
        #get filesize
        fsize = self.inFile.filesize()
               
        #start parsing
        self.inFile.seek(0)
        magic = self.inFile.read_string(3)
        version = self.inFile.read_byte()
        print magic, version
        if version == 0:
            self.odfFormat = 4
        key = self.inFile.read_string(8)
        
        while self.inFile.tell() != fsize:
            tag = self.inFile.read_string(4)
            sectLen = self.inFile.read_long()       
            print "==%s==" %tag, sectLen
            if tag == "MAT ":
                self.parse_materials(sectLen)
            elif tag == "TEX ":
                self.parse_textures(sectLen)
            elif tag == "MESH":
                self.parse_meshes(sectLen)
            elif tag == "FRAM":
                #self.parse_frames(sectLen)
                self.inFile.seek(sectLen, 1)
            elif tag == "BANM":
                self.inFile.seek(sectLen + 264, 1)
            elif tag == "ENVL":
                self.inFile.seek(sectLen, 1)
                #self.parse_bones(sectLen)
            else:
                self.inFile.seek(sectLen, 1)
            
def check_encryption(string):
    
    return string != "ODF"
    
def build_key(buf):
    
    key = [buf[0] ^ 0x4F,
       buf[1] ^ 0x44,
       buf[2] ^ 0x46,
       (buf[13] ^ 0x41) - 0xA,
       buf[4] ^ 0x00]

    for i in xrange(5):
        key.append(key[i] + 0x05)
        
    for i in xrange(5):
        key.append(key[i] + 0x0A)
    return key

def decrypt_file(inFile):
    
    inFile.seek(0)
    buf = bytearray(inFile.read(15))
    key = build_key(buf)
    inFile.seek(0)
    buf = bytearray(inFile.read_ubyte(inFile.filesize()))
    tempFile = open("temp", "wb")
    for j in xrange(len(buf)):
        buf[j] = buf[j] ^ key[j % 15]
        key[j % 15] = (key[j % 15] + 0x0F) & 0xFF
    tempFile.write(buf)
    tempFile.close()
    
def read_file(path):
    '''Read the file'''
    
    openFile = StructReader(open(path, 'rb'))
    buf = openFile.read_string(3)
    encrypted = check_encryption(buf)
    if encrypted:
        decrypt_file(openFile)
        openFile.close()
        openFile = StructReader(open("temp", 'rb'))
        
    obj = teatime_odf(inFile=openFile)
    obj.parse_file()
    openFile.close()
    if encrypted:
        os.remove("temp")
    return obj

def write_file(path):
    
    pass

def definitions():
    '''Return the header, extension, and a description of the format'''
    
    return "ODF", "ODF", "Teatime ODF"